import os
import glob

try:
    import pygame
    NO_SOUND = False
except ImportError:
    NO_SOUND = True

# This next line is designed to give serious music people corinaries.
NOTES = list('0123456789abc')   # Yeah, hexidecimal... sorry.

MAX_NOTES = 2

class SoundError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

class Song(object):
    def __init__(self, filename):
        lines = [line.strip() for line in open(filename, 'rU').readlines()]

        self.name = lines[0]
        self.difficulty = int(lines[1])
        self.rate = int(lines[2])

        self.notes = []
        self.comments = []
        for line in lines[3:]:
            if '#' in line:
                notes, comment = line.split('#', 1)
                notes = notes.strip()
            else:
                notes, comment = line, ''

            if notes.startswith('.'):
                notes = ''  # Rest

            self.notes.append([NOTES.index(n) for n in list(notes)])
            self.comments.append(comment)

        self.reset()

    def __str__(self):
        return self.name

    def __cmp__(self, other):
        if self.difficulty == other.difficulty:
            return cmp(self.name, other.name)
        return cmp(self.difficulty, other.difficulty)

    def reset(self):
        self.ix = 0
        self.time = 0
        self.playing = False
        self.done = False

    def play(self, delta):
        self.playing = self.ix < len(self.notes)
        self.done = not self.playing

        notes = []
        comment = ''
        if self.playing:
            self.time -= delta

            if self.time <= 0:
                self.time = self.rate
                notes = self.notes[self.ix]
                comment = self.comments[self.ix]
                self.ix += 1

        return notes, comment

    @property
    def progress(self):
        return self.ix / float(len(self.notes))

class Jukebox(object):
    def __init__(self, path='sounds'):
        if not NO_SOUND:
            pygame.mixer.set_num_channels(2 * (MAX_NOTES + 1))

        self.notes = dict()
        for ix, note in enumerate(NOTES):
            good = load_note(path, 'note_%s' % note)
            bad = load_note(path, 'bad_%s' % note)
            self.notes[ix] = dict(good=good, bad=bad)

        self.event_sounds = load_event_sounds(path)

        self.playing = []
        self.current_event = None

    def play(self, ix, good):
        if NO_SOUND: return

        chan = pygame.mixer.find_channel(True)

        if good:
            chan.play(self.notes[ix]['good'])
            self.playing.append(chan)
        else:
            chan.play(self.notes[ix]['bad'])

        self.playing = [c for c in self.playing if c.get_busy()]

        if len(self.playing) > MAX_NOTES:
            stop = self.playing.pop(0)
            stop.fadeout(100)
            #del stop

    def event(self, name):
        if NO_SOUND: return

        if name in self.event_sounds:
            if (self.current_event is not None) and self.current_event.get_busy():
                self.current_event.fadeout(500)
            chan = pygame.mixer.find_channel(True)
            chan.play(self.event_sounds[name])
            self.current_event = chan

    def event_song(self, name):
        if NO_SOUND: return
        return self.event_sounds.get(name)

    def stop(self):
        if not NO_SOUND: 
            pygame.mixer.stop()
        
    def pause(self):
        if not NO_SOUND: 
            pygame.mixer.pause()

    def fadeout(self, time):
        if not NO_SOUND: 
            pygame.mixer.fadeout(time)

    @property
    def busy(self):
        if not NO_SOUND: 
            return pygame.mixer.get_busy()


def load_note(path, name):
    if NO_SOUND: return None

    for ext in ('wav', 'ogg'):
        filename = os.path.join(path, '.'.join((name, ext)))
        try:
            note = pygame.mixer.Sound(filename)
            return note
        except pygame.error:
            pass
   
    msg = 'Cannot load: %s.[ogg|wav]' % (os.path.join(path, name))
    raise SoundError(msg)

def load_event_sounds(path):
    events = dict()
    if NO_SOUND: return events

    wavs = glob.glob(os.path.join(path, 'event_*.wav'))
    oggs = glob.glob(os.path.join(path, 'event_*.ogg'))

    for file in wavs + oggs:
        event = os.path.splitext(os.path.basename(file))[0][6:]
        try:
            events[event] = pygame.mixer.Sound(file)
        except pygame.error:
            pass

    return events

def test_song(filename):
    song = Song(filename)
    print "Name:", song.name
    print "Difficulty:", song.difficulty
    print "Rate:", song.rate

    juke = Jukebox()
    
    while not song.done:
        notes, comment = song.play(song.rate+1)
        play = []
        for ix in range(len(NOTES)):
            if ix in notes:
                play.append(NOTES[ix])
            else:
                play.append(' ')
        print ''.join(play), comment

        for note in notes:
            juke.play(note, True)
        time.sleep(song.rate/1000.0)

    juke.fadeout(1500)
    while juke.busy:
        time.sleep(0.1)


if __name__ == '__main__':
    import sys
    import time
    from pprint import pprint

    pygame.mixer.init()
    test_song(sys.argv[1])

#    juke = Jukebox()
#    pprint(juke.notes)
#    for good in (True, False):
#        print "Good:", good
#        for i in range(len(NOTES)):
#            print "Note:", NOTES[i]
#            juke.play(i, good)
#            time.sleep(.2)
#
#        while pygame.mixer.get_busy(): 
#            time.sleep(.1)
#        print 








